fix: evict idle HTTP sessions instead of leaking them - #19
Merged
Conversation
Sessions were only ever removed on an explicit DELETE /mcp or on transport.close(). Most MCP clients (claude.ai, Claude Desktop) send neither - they just stop talking - so every session stayed in the map along with the Server instance behind it. On the dev host this showed as 449 "Session initialized" log lines and zero closes of either kind, with activeSessions climbing monotonically since deploy. Add SessionRegistry: last-seen tracking per session plus a periodic sweep that closes and evicts anything idle past SESSION_IDLE_TIMEOUT_MS (default 30min, 0 disables). Sessions holding an open SSE stream are never swept however quiet they are - streaming is activity, and evicting one would kill a live connection. The sweep removes the entry before calling close(), so a throwing close() cannot leave the entry behind. /health now also reports streamingSessions and a cumulative sweptSessions so the behaviour is observable in production.
This was referenced Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
HTTP transport sessions were only removed on an explicit
DELETE /mcpor ontransport.close(). Most MCP clients (claude.ai, Claude Desktop) send neither — they just stop talking — so every session stayed in the map along with theServerinstance behind it.Observed on the dev host: 449
Session initializedlog lines and zero closes of either kind, withactiveSessionsclimbing monotonically since deploy.61 MiB resident at the time of diagnosis — not yet harmful, but unbounded.
Fix
New
src/session-registry.ts: last-seen tracking per session plus a periodic sweep that closes and evicts anything idle pastSESSION_IDLE_TIMEOUT_MS.Two details that matter more than the sweep itself:
GET /mcpstream can sit quiet for hours; sweeping on last-request-time would kill live connections. The registry counts open streams and restarts the idle clock when the last one closes.close(). Aclose()that throws on an already-dead socket would otherwise leave the entry behind, reintroducing the exact leak. Covered by a test.Config
SESSION_IDLE_TIMEOUT_MS1800000(30min)0disables sweeping entirelySESSION_SWEEP_INTERVAL_MS300000(5min)Observability
/healthnow also reportsstreamingSessionsand a cumulativesweptSessions, so after deployactiveSessionsshould plateau rather than climb.Testing
15 new unit tests covering eviction, the within-timeout and recent-traffic cases, open/multiple/closed streams, failing
close(), disabled sweeping, and a regression test that drains 449 abandoned sessions.540 tests passing (was 525). Lint and format clean.